﻿using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Tools.Debugging;

namespace repliback4.Net {

    public class AsyncEventData {
        public AsyncConnection connection;
        public AsyncEventData ( AsyncConnection connection ) {
            this.connection = connection;
        }

    }

    public class AsyncConnectionException : Exception {
        protected SocketError error;
        public SocketError Error {
            get { return this.error; }
        }
        public AsyncConnectionException ( String message, SocketError error )
            : base( message ) {
            this.error = error;
        }
    }

    public class Response : AsyncEventData {
        protected String data;
        protected SocketError error = SocketError.Success;

        public Response ( AsyncConnection sender, String data, SocketError error )
            : base( sender ) {
            this.data = data;
            this.error = error;
        }
        public Response ( AsyncConnection sender, String data )
            : base( sender ) {
            this.connection = sender;
            this.data = data;
        }

        public AsyncConnection Connection {
            get { return this.connection; }
        }

        public String Data {
            get { return this.data; }
        }

        public SocketError Error {
            get { return this.error; }
        }

    }

    public class Receiver {
        public static int bufferSize = 1024;
        public StringBuilder builer = new StringBuilder( );
        public Byte[] buffer = new Byte[Receiver.bufferSize];
        public String data {
            get {
                return this.builer.ToString( );
            }
        }

        public void ClearBuffer ( ) {
            //this.buffer = new Byte[Receiver.bufferSize];
        }
    }

    public class DisconnectData : AsyncEventData {
        public DisconnectData ( AsyncConnection connection ) : base( connection ) { }
    }

    public class RemoteDisconnectData : AsyncEventData {
        public RemoteDisconnectData ( AsyncConnection connection ) : base( connection ) { }
    }

    public class ConnectData : AsyncEventData {
        public ConnectData ( AsyncConnection connection ) : base( connection ) { }
    }

    public class AsyncConnection : IDisposable {

        protected Stream stream;

        protected NetworkStream netStream;
        protected SslStream sslStream;
        protected BufferedStream buffStream;

        //protected StreamReader streamReader;
        protected StreamWriter streamWriter;
        public TcpClient client;
        protected Encoding encoding = Encoding.UTF8;
        //protected Decoder decoder;
        //protected Encoder encoder;
        //IAsyncResult readResult;


        public delegate void OnReceiveHandler ( Response response );
        public event OnReceiveHandler OnReceive;

        public delegate void OnConnectHandler ( ConnectData data );
        public event OnConnectHandler OnConnect;


        public delegate void OnDisconnectHandler ( DisconnectData data );
        public event OnDisconnectHandler OnDisconnect;

        public delegate void OnRemoteDisconnectHandler ( RemoteDisconnectData data );
        public event OnRemoteDisconnectHandler OnRemoteDisconnect;


        protected void CallOnConnect ( ConnectData data ) {
            if ( this.OnConnect != null ) this.OnConnect( data );
        }

        public void Reconnect ( ) {
            this.CallOnConnect( new ConnectData( this ) );
        }

        public AsyncConnection ( TcpClient client ) {

            this.client = client;

        }

        public void Start ( ) {
            IPEndPoint rep = ( IPEndPoint ) this.client.Client.RemoteEndPoint;
            Logger.Debug( this, "Reading started, remote client: {0}:{1}", rep.Address.ToString( ), rep.Port );

            this.netStream = this.client.GetStream( );
            this.stream = this.netStream;

            this.BeginReadStream( );

            this.CallOnConnect( new ConnectData( this ) );
        }

        public void Stop ( ) {
            DisconnectData data = new DisconnectData( this );
            if ( this.OnDisconnect != null ) this.OnDisconnect( data );
            this.client.Close( );
        }

        protected void BeginReadStream ( ) {
            Receiver r = new Receiver( );
            this.BeginReadStream( r );
        }

        protected void BeginReadStream ( Receiver r ) {
            //Logger.DebugFormat( this, "BeginReadStream pre : buffer:{0}", this.encoding.GetString( r.buffer ) );
            r.ClearBuffer( );
            //Logger.DebugFormat( this, "BeginReadStream post: buffer:{0}", this.encoding.GetString( r.buffer ) );

            /*this.readResult = */
            this.stream.BeginRead( r.buffer, 0, Receiver.bufferSize, new AsyncCallback( this.AsyncReceiveStream ), r );
        }
        //protected void StopReadStream ( ) {
        //    try {
        //        this.AsyncEndReceiveStream( this.readResult );
        //    } catch ( Exception e ) {
        //        Logger.DebugFormat( this, "StopReadStream Exception: {0}", e.Message );
        //    }
        //}

        protected void AsyncEndReceiveStream ( IAsyncResult result ) {

            //Boolean transmissionEnd = this.client.Available == 0;
            //            ((SslStream)this.netStream).
            Receiver r = ( Receiver ) result.AsyncState;



            int rx = this.stream.EndRead( result );

            Boolean transmissionEnd = rx < Receiver.bufferSize;

            if ( rx > 0 ) {
                String b = this.encoding.GetString( r.buffer, 0, rx );
                r.builer.Append( b );

                //StringBuilder __sb = new StringBuilder( );
                //foreach ( Byte bb in r.buffer ) {
                //    __sb.AppendFormat( "[{0:x2}|{1} ] ", bb, Char.ConvertFromUtf32(bb));

                //}

                //Logger.DebugFormat( this, "[{0}] - l:{1}, transEnd: {2}", b, rx, transmissionEnd );



                if ( transmissionEnd ) {
                    Response response = new Response( this, r.data );

                    if ( this.OnReceive != null ) this.OnReceive( response );
                    r.builer.Length = 0;

                } else {

                    this.BeginReadStream( r );
                }
                //return transmissionEnd;
            } else {
                throw new AsyncConnectionException( "disconnected", SocketError.ConnectionReset );
            }
            //  return true;
        }

        protected void AsyncReceiveStream ( IAsyncResult result ) {
            try {
                this.AsyncEndReceiveStream( result );
                this.BeginReadStream( );
            } catch ( AsyncConnectionException e ) {
                switch ( e.Error ) {
                    case SocketError.ConnectionReset:
                        if ( this.OnRemoteDisconnect != null ) this.OnRemoteDisconnect( new RemoteDisconnectData( this ) );
                        break;
                    default:
                        throw e;
                }
            } catch ( SocketException e ) {
                Response response = new Response( this, "", e.SocketErrorCode );
                this.OnReceive( response );
                Logger.Debug( this, "AsyncReceiveStream: Socket Exception:", e );
            } catch ( InvalidOperationException e ) {
                Logger.Exception( this, "AsyncReceiveStream: Invalid Operation:", e );
            } catch ( Exception e ) {
                if ( e.InnerException != null ) {
                    try {
                        throw e.InnerException;
                    } catch ( SocketException innerE ) {
                        switch ( innerE.SocketErrorCode ) {
                            case SocketError.ConnectionReset:
                                Logger.Debug( this, "!!!Connection reset" );
                                break;
                            default:
                                Logger.Exception( this, "AsyncReceiveStream: Exception:", innerE );
                                break;
                        }
                    } catch ( Exception innerE ) {
                        Logger.Exception( this, "AsyncReceiveStream: InnerException", innerE );
                    }

                } else {
                    Logger.Exception( this, "AsyncReceiveStream: Exception:", e );
                }
            }
        }

        //protected void BeginReadSocket ( Receiver r ) {
        //    //TcpClient c;
        //    this.client.Client.BeginReceive( r.buffer, 0, Receiver.bufferSize, SocketFlags.None, new AsyncCallback( this.AsyncReceiveSocket ), r );
        //}
        //protected void AsyncReceiveSocket ( IAsyncResult result ) {
        //    try {
        //        Boolean transmissionEnd = this.client.Available == 0;
        //        int rx = this.client.Client.EndReceive( result );
        //        if ( rx > 0 ) {
        //            Receiver r = ( Receiver ) result.AsyncState;
        //            r.builer.Append( this.encoding.GetChars( r.buffer, 0, rx ) );
        //            this.BeginReadSocket( r );
        //            if ( transmissionEnd ) {
        //                Response response = new Response( this, r.data );

        //                if ( this.OnReceive != null ) this.OnReceive( response );
        //                r.builer.Length = 0;

        //            }
        //        } else {
        //            if ( this.OnRemoteDisconnect != null ) this.OnRemoteDisconnect( new RemoteDisconnectData( this ) );
        //        }
        //    } catch ( SocketException e ) {
        //        Response response = new Response( this, "", e.SocketErrorCode );
        //        this.OnReceive( response );
        //        Logger.DebugFormat( this, "AsyncReceiveSocket, Socket Exception: {0}", e.Message );
        //    } catch ( InvalidOperationException e ) {
        //        Logger.DebugFormat( this, "AsyncReceiveSocket, Invalid Operation: {0}", e.Message );
        //    } catch ( Exception e ) {
        //        Logger.DebugFormat( this, "AsyncReceiveSocket, Exception: {0}", e.Message );
        //    }

        //}

        public StreamWriter StreamWriter {
            get {
                // if ( this.streamWriter == null ) {
                if ( this.stream != null ) {
                    this.streamWriter = new StreamWriter( this.stream );
                    this.streamWriter.AutoFlush = false;
                } else {
                    throw new Exception( "not connected, netstream not initialized" );
                }
                //}
                return this.streamWriter;

            }
        }

        public void Send ( string message ) {
            try {
                this.streamWriter.Write( message );
            } catch ( IOException e ) {
                try {
                    throw e.InnerException;
                } catch ( SocketException se ) {
                    Logger.Exception( this, String.Format( "msg:{0}\nsocketcode:{1}, code:{2}", se.Message, se.SocketErrorCode, se.NativeErrorCode ), e );
                    switch ( se.SocketErrorCode ) {
                        case SocketError.ConnectionReset:
                            this.OnRemoteDisconnect( new RemoteDisconnectData( this ) );
                            break;
                        case SocketError.ConnectionAborted:
                            Logger.Debug( this, "connection aborted" );
                            break;
                        default:
                            Logger.Debug( this, String.Format( "UNKNOWN\nmsg:{0}\nsocketcode:{1}, code:{2}", se.Message, se.SocketErrorCode, se.NativeErrorCode ) );
                            break;
                    }
                }
            }
        }



        #region IDisposable Members

        public void Dispose ( ) {
            if ( this.streamWriter != null ) { this.streamWriter.Close( ); this.streamWriter.Dispose( ); }
            if ( this.sslStream != null ) { this.sslStream.Close( ); this.sslStream.Dispose( ); }
            if ( this.netStream != null ) { this.netStream.Close( ); this.netStream.Dispose( ); }
            if ( this.stream != null ) { this.stream.Close( ); this.stream.Dispose( ); }
        }

        #endregion
    }
}
